package com.wutianyi.favority;

import static com.wutianyi.favority.constants.Constants.*;
import static com.wutianyi.favority.rel.RelTypes.*;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.neo4j.cypher.javacompat.ExecutionEngine;
import org.neo4j.cypher.javacompat.ExecutionResult;
import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.Transaction;
import org.neo4j.graphdb.index.Index;

import com.wutianyi.favority.bean.Folder;
import com.wutianyi.favority.bean.Link;
import com.wutianyi.favority.bean.User;
import com.wutianyi.favority.constants.Constants;
import com.wutianyi.favority.rel.RelTypes;

public class FavorityRepository
{

    private final GraphDatabaseService graphDb;

    /**
     * 用户根节点
     */
    private Node userRoot;
    /**
     * 链接根节点
     */
    private Node linkRoot;

    /**
     * 用户信息索引
     */
    private Index<Node> userIndex;

    /**
     * 链接索引
     */
    private Index<Node> linkIndex;

    private ExecutionEngine engine;

    /**
     * @param graphDb
     */
    public FavorityRepository(GraphDatabaseService graphDb)
    {
        this.graphDb = graphDb;
        engine = new ExecutionEngine(graphDb);
        userIndex = graphDb.index().forNodes("users");
        linkIndex = graphDb.index().forNodes("links");
        init();
    }

    private void init()
    {
        // 创建user的根节点
        userRoot = userIndex.get(Constants.USER_ID_INDEX, Constants.USER_ROOT_ID).getSingle();
        if (null == userRoot)
        {
            userRoot = createNode(graphDb.getReferenceNode(), userIndex, USER, USER_ID_INDEX, USER_ROOT_ID, null);
        }
        // 创建link的根节点
        linkRoot = linkIndex.get(LINK_INDEX, LINK_ROOT_VALUE).getSingle();
        if (null == linkRoot)
        {
            linkRoot = createNode(graphDb.getReferenceNode(), linkIndex, LINK, LINK_INDEX, LINK_ROOT_VALUE, null);
        }
    }

    /**
     * 创建节点 不进行唯一性的检查
     * 
     * @param name
     * @param value
     * @param params
     * @return
     */
    private Node createNode(Node parentNode, Index<Node> index, RelTypes relTypes, String name, Object value,
            Map<String, Object> params)
    {
        Transaction tx = graphDb.beginTx();
        try
        {
            Node node = newNode(parentNode, index, relTypes, name, value, params);
            tx.success();
            return node;
        }
        finally
        {
            tx.finish();
        }
    }

    private Node newNode(Node parentNode, Index<Node> index, RelTypes relTypes, String name, Object value,
            Map<String, Object> params)
    {
        Node node = graphDb.createNode();
        node.setProperty(name, value);
        if (null != parentNode)
        {
            parentNode.createRelationshipTo(node, relTypes);
        }
        if (null != index)
        {
            index.add(node, name, value);
        }

        if (null != params)
        {
            for (Entry<String, Object> entry : params.entrySet())
            {
                node.setProperty(entry.getKey(), entry.getValue());
            }
        }
        return node;
    }

    /**
     * 获取用户，如果没有则创建相应的节点
     * 
     * @param id
     * @return
     */
    public User createUser(long id)
    {
        Node userNode = userIndex.get(USER_ID_INDEX, id).getSingle();
        if (null == userNode)
        {
            Transaction tx = graphDb.beginTx();
            try
            {
                userNode = newNode(userRoot, userIndex, USER, USER_ID_INDEX, id, null);
                userRoot.setProperty("count", ((Integer) userRoot.getProperty("count", 0)) + 1);
                tx.success();
            }
            finally
            {
                tx.finish();
            }

        }

        return new User(userNode, this);
    }

    /**
     * 方法里面没有事物的控制，需要调用者自己提供事物的控制
     * 
     * @param value
     * @return
     */
    public Link newLink(String value)
    {
        Node linkNode = linkIndex.get(LINK_INDEX, value).getSingle();
        if (null == linkNode)
        {
            linkNode = newNode(linkRoot, linkIndex, LINK, LINK_INDEX, value, null);
            linkRoot.setProperty("count", ((Integer) linkRoot.getProperty("count", 0)) + 1);

        }
        return new Link(linkNode);
    }

    /**
     * 获取指定的链接，没有则创建相应的节点
     * 
     * @param value
     * @return
     */
    public Link createLink(String value)
    {
        Node linkNode = linkIndex.get(LINK_INDEX, value).getSingle();
        if (null == linkNode)
        {
            Transaction tx = graphDb.beginTx();
            try
            {
                linkNode = newNode(linkRoot, linkIndex, LINK, LINK_INDEX, value, null);
                linkRoot.setProperty("count", ((Integer) linkRoot.getProperty("count", 0)) + 1);
                tx.success();
            }
            finally
            {
                tx.finish();
            }

        }
        return new Link(linkNode);
    }

    public Folder createFolder(Node parentNode, String name)
    {
        return new Folder(createNode(parentNode, null, FOLDER, Constants.FOLDER_NAME, name, null));
    }

    /**
     * 调用者提供事物控制
     * 
     * @param node1
     * @param node2
     * @param relTypes
     * @return
     */
    public Relationship newRelationShip(Node node1, Node node2, RelTypes relTypes)
    {
        Iterable<Relationship> rs = node1.getRelationships(relTypes, Direction.OUTGOING);

        for (Relationship r : rs)
        {
            if (r.getEndNode().equals(node2))
            {
                return r;
            }
        }

        Relationship r = node1.createRelationshipTo(node2, relTypes);
        return r;
    }

    /**
     * 为两个节点创建指定的关系
     * 
     * @param node1
     * @param node2
     * @return
     */
    public Relationship createRelationshipTo(Node node1, Node node2, RelTypes relTypes)
    {
        Transaction tx = graphDb.beginTx();
        try
        {
            Iterable<Relationship> rs = node1.getRelationships(relTypes, Direction.OUTGOING);

            for (Relationship r : rs)
            {
                if (r.getEndNode().equals(node2))
                {
                    return r;
                }
            }

            Relationship r = node1.createRelationshipTo(node2, relTypes);
            tx.success();
            return r;
        }
        finally
        {
            tx.finish();
        }
    }

    /**
     * 调用者控制事务
     * 
     * @param node1
     * @param node2
     * @param relTypes
     * @return
     */
    public int deleteRelationTo(Node node1, Node node2, RelTypes relTypes)
    {

        Iterable<Relationship> rs = node1.getRelationships(relTypes, Direction.OUTGOING);
        for (Relationship r : rs)
        {
            if (r.getEndNode().equals(node2))
            {
                r.delete();
            }
        }
        return 1;

    }

    /**
     * 删除两个节点中指定的关系
     * 
     * @param node1
     * @param node2
     * @param relTypes
     * @return
     */
    public int removeRelationTo(Node node1, Node node2, RelTypes relTypes)
    {
        Transaction tx = graphDb.beginTx();

        try
        {
            Iterable<Relationship> rs = node1.getRelationships(relTypes, Direction.OUTGOING);
            for (Relationship r : rs)
            {
                if (r.getEndNode().equals(node2))
                {
                    r.delete();
                }
            }
            tx.success();
            return 1;
        }
        finally
        {
            tx.finish();
        }

    }

    public User getUser(long id)
    {
        Node userNode = userIndex.get(USER_ID_INDEX, id).getSingle();
        if (null != userNode)
        {
            return new User(userNode, this);
        }

        return null;
    }

    public Link getLink(String value)
    {
        Node linkNode = linkIndex.get(LINK_INDEX, value).getSingle();
        if (null != linkNode)
        {
            return new Link(linkNode);
        }
        return null;
    }

    public Index<Node> getUserIndex()
    {
        return userIndex;
    }

    public Index<Node> getLinkIndex()
    {
        return linkIndex;
    }

    /**
     * 获取推荐的信息
     * 由于count的计算会存在偏差，所以需要定期去更新相应的值
     * @return
     */
    public List<Link> getRecommendLinks(long uid)
    {

        int linkCount = (Integer) linkRoot.getProperty("count", 0);
        int userCount = (Integer) userRoot.getProperty("count", 1);
        // 计算推荐的比例
        int l = (int) (linkCount / userCount * 0.6);

        String query = "start r=node({id}) match r-[:LINK]->l<-[:LINKED]-(x)<-[:FOLDER]-(u) where l.count >={rate} and u.uii!={uid} return l, l.count limit 10";
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("uid", uid);
        params.put("id", linkRoot.getId());
        params.put("rate", l);
        ExecutionResult result = engine.execute(query, params);
        Iterator<Map<String, Object>> itr = result.iterator();
        List<Link> recommendLinks = new ArrayList<Link>();
        while (itr.hasNext())
        {
            Map<String, Object> m = itr.next();
            Link link = new Link((Node) m.get("l"));
            link.setCount((Integer) m.get("count(x)"));
            recommendLinks.add(link);
        }
        return recommendLinks;
    }

    /**
     * 获取推荐的用户的信息 
     * 
     * @return
     */
    public List<User> getRecommendUsers(long userId)
    {
        Node userNode = userIndex.get(Constants.USER_ID_INDEX, userId).getSingle();
        String query = "start u=node({id}) match u-[:FOLDER]->(a)-[:LINKED]->(l)<-[:LINKED]-(x)<-[:FOLDER]-(f) where f.uii!={uid} return f,count(f) order by count(f) desc limit 10";
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("id", userNode.getId());
        params.put("uid", userId);
        ExecutionResult result = engine.execute(query, params);
        Iterator<Map<String, Object>> itr = result.iterator();
        List<User> recommendUsers = new ArrayList<User>();
        while (itr.hasNext())
        {
            Map<String, Object> m = itr.next();
            User user = new User((Node) m.get("f"), this);
            user.setSameLinkCount((Integer) m.get("count(f)"));
            recommendUsers.add(user);
        }
        return recommendUsers;

    }

    public GraphDatabaseService getGraphDb()
    {
        return graphDb;
    }

    public Node getUserRoot()
    {
        return userRoot;
    }

    public Node getLinkRoot()
    {
        return linkRoot;
    }

}
